package com.atguigu.gmall.activity.service.impl;

import com.alibaba.nacos.common.utils.StringUtils;
import com.atguigu.gmall.activity.mapper.SeckillGoodsMapper;
import com.atguigu.gmall.activity.service.SeckillGoodsService;
import com.atguigu.gmall.activity.util.CacheHelper;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import com.atguigu.gmall.common.util.MD5;
import com.atguigu.gmall.model.activity.OrderRecode;
import com.atguigu.gmall.model.activity.SeckillGoods;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import io.lettuce.core.RedisClient;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

@Service
public class SeckillGoodsServiceImpl implements SeckillGoodsService {


    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private SeckillGoodsMapper seckillGoodsMapper;
    @Autowired
    private RedissonClient redissonClient;

    @Override
    public List<SeckillGoods> findAll() {
        //  hvals key
        List<SeckillGoods> seckillGoodsList = this.redisTemplate.boundHashOps(RedisConst.SECKILL_GOODS).values();
        return seckillGoodsList;
    }

    @Override
    public SeckillGoods getSeckillGoods(Long skuId) {
        //  hget key field 细节错了！
        SeckillGoods seckillGoods = (SeckillGoods) this.redisTemplate.boundHashOps(RedisConst.SECKILL_GOODS).get(skuId.toString());
        return seckillGoods;
    }

    @Override
    public void seckillOrder(String userId, Long skuId) {
        /*
        1.  判断状态位！
        2.  判断用户是否下过订单！ setnx：
        3.  获取库存！
        4.  将订单记录写入缓存！
        5.  秒杀成功要更新库存！
         */
        //  校验 状态位！ skuId:status  status 0 or 1
        String status = (String) CacheHelper.get(skuId.toString());
        if (StringUtils.isEmpty(status) || "0".equals(status)) {
            return;
        }

        //   判断用户是否下过订单！ setnx：
        //  key  = seckill:user:userId
        String userKey = RedisConst.SECKILL_USER + userId;
        Boolean result = this.redisTemplate.opsForValue().setIfAbsent(userKey, skuId.toString(), RedisConst.SECKILL__TIMEOUT, TimeUnit.SECONDS);
        //  判断用户是否下过订单！ result = true
        if (!result) {
            //  说明用户下过订单了！
            return;
        }

        //  库存存储在redis  list ！
        String stockSkuId = (String) this.redisTemplate.opsForList().rightPop(RedisConst.SECKILL_STOCK_PREFIX + skuId);
        //  如果为空，说明当前商品已经售罄
        if (StringUtils.isEmpty(stockSkuId)) {
            //  通知其他兄弟节点，该商品已经售罄！  发布订阅
            this.redisTemplate.convertAndSend("seckillpush", skuId + ":0");
            return;
        }

        //  将数据写入缓存！
        OrderRecode orderRecode = new OrderRecode();

        //  每个人只能秒杀1件商品！
        orderRecode.setNum(1);
        orderRecode.setUserId(userId);
        orderRecode.setSeckillGoods(this.getSeckillGoods(skuId));
        orderRecode.setOrderStr(MD5.encrypt(userId + skuId));

        //  确定数据类型！ hash key = seckill:orders  field = userId  value = orderRecode
        this.redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS).put(userId, orderRecode);

        //  更新库存！ redis ， mysql！
        this.updateStockCount(skuId);
    }

    //检查订单
    @Override
    public Result checkOrder(Long skuId, String userId) {
      /*
      1.判断用户是否存在
      2.判断用户是否抢购成功
      3.判断用户是否下过订单！
      4.判断状态位
      */
      //先判断用户是否存在 看缓存中是否有用户对应的key！
        Boolean flag = this.redisTemplate.hasKey(RedisConst.SECKILL_USER + userId);

        //  flag = true ;表示用户存在了！
        if (flag){
            //  只有用户存在了，才能判断用户是否抢购成功！ 判断用户是否有预下单记录！
            Boolean result = this.redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS).hasKey(userId);
            //  result = true 说明用户有预下单记录，则抢购成功！ 去下单！
            if (result){
                //  获取订单记录
                OrderRecode orderRecode = (OrderRecode) this.redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS).get(userId);
                //  返回数据
                return Result.build(orderRecode, ResultCodeEnum.SECKILL_SUCCESS);
            }
        }

        //  判断用户是否下过订单！ 意味着在缓存中有真正的订单记录，同时在数据库中也有记录！
        //  存储真正下单的数据类型  Hash  key = RedisConst.SECKILL_ORDERS_USERS field = userId  value = orderId
        Boolean exist = this.redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS_USERS).hasKey(userId);
        //  exist = true； 说明用户已经下过订单了。 提示抢购成功，去看我的订单
        if (exist){
            //  获取订单Id
            String orderId = (String) this.redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS_USERS).get(userId);
            //  返回数据
            return Result.build(orderId, ResultCodeEnum.SECKILL_ORDER_SUCCESS);
        }

        //  校验 状态位！ skuId:status  status 0 or 1
        String status = (String) CacheHelper.get(skuId.toString());
        if(StringUtils.isEmpty(status) || "0".equals(status)){
            //  返回数据
            return Result.build(null, ResultCodeEnum.SECKILL_FINISH);
        }
        //  默认排队中
        return Result.build(null, ResultCodeEnum.SECKILL_RUN);
    }

    //  更新库存！
    //如果感觉mysql扛不住加分布式锁 1、redis 2、redission
    @Transactional(rollbackFor = Exception.class)
    public void updateStockCount(Long skuId) {
        //集群部署设置锁
        RLock lock = this.redissonClient.getLock("lock:" + skuId);
        //非集群部署设置锁
        //lock lock = new ReentrantLock(true);可重入锁
        lock.lock();
        //update seckill_goods set stock_count = ? where sku_id = ?;剩余库存
        //计算list的长度    //  list  key = seckill:stock:skuId  value = skuId    seckill:stock:skuId 22
        Long count = this.redisTemplate.opsForList().size(RedisConst.SECKILL_STOCK_PREFIX + skuId);
        //为了避免频繁的更新mysql和redis
        try {
            if (count % 2 == 0) {

                //更新数据库
                SeckillGoods seckillGoods = new SeckillGoods();
                seckillGoods.setStockCount(count.intValue());//设置库存的数量
                QueryWrapper<SeckillGoods> seckillGoodsQueryWrapper = new QueryWrapper<>();
                seckillGoodsQueryWrapper.eq("skuId", skuId);
                seckillGoodsMapper.update(seckillGoods, seckillGoodsQueryWrapper);
                //更新redis缓存 hget key field   hset key field value
                SeckillGoods seckillGoodsUpd = this.getSeckillGoods(skuId);
                seckillGoodsUpd.setStockCount(count.intValue());
                this.redisTemplate.opsForHash().put(RedisConst.SECKILL_GOODS, skuId.toString(), seckillGoods);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //解锁
            lock.unlock();
        }
    }
}